package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"

	"chinaport-proxy/protocol"
)

type PayExchangeInfoHead struct {
	GUID             string      `json:"guid,omitempty"`
	InitalRequest    string      `json:"initalRequest,omitempty"`
	InitalResponse   string      `json:"initalResponse,omitempty"`
	EbpCode          string      `json:"ebpCode,omitempty"`
	PayCode          string      `json:"payCode,omitempty"`
	PayTransactionID string      `json:"payTransactionId,omitempty"`
	TotalAmount      float64     `json:"totalAmount,omitempty"`
	Currency         string      `json:"currency,omitempty"`
	VerDept          string      `json:"verDept,omitempty"`
	PayType          string      `json:"payType,omitempty"`
	TradingTime      string      `json:"tradingTime,omitempty"`
	Note             string      `json:"note,omitempty"`
	OrderNo          string      `json:"orderNo,omitempty"`
	GoodsInfo        []GoodsItem `json:"goodsInfo,omitempty"`
	RecpAccount      string      `json:"recpAccount,omitempty"`
	RecpCode         string      `json:"recpCode,omitempty"`
	RecpName         string      `json:"recpName,omitempty"`
}

type PayExchangeInfoItem struct {
	OrderNo     string      `json:"orderNo,omitempty"`
	GoodsInfo   []GoodsItem `json:"goodsInfo,omitempty"`
	RecpAccount string      `json:"recpAccount,omitempty"`
	RecpCode    string      `json:"recpCode,omitempty"`
	RecpName    string      `json:"recpName,omitempty"`
}

type GoodsItem struct {
	GName    string `json:"gname,omitempty"`
	ItemLink string `json:"itemLink,omitempty"`
}

type Customs179Request struct {
	SessionID            string                `json:"sessionId" binding:"required"`
	PayExchangeInfoHead  PayExchangeInfoHead   `json:"payExchangeInfoHead" binding:"required"`
	PayExchangeInfoLists []PayExchangeInfoItem `json:"payExchangeInfoLists"`
	ServiceTime          int64                 `json:"serviceTime" binding:"required"`
	RealTimeDataUpload   bool                  `json:"realTimeDataUpload"`
}

type Customs179Response struct {
	SessionID   string      `json:"sessionID"`
	ServiceTime int64       `json:"serviceTime"`
	GUID        string      `json:"guid"`
	CertNo      string      `json:"certNo"`
	SignValue   string      `json:"signValue"`
	Customs     interface{} `json:"customs,omitempty"`
	Success     bool        `json:"success"`
	Error       string      `json:"error,omitempty"`
}

func (h *ProxyHandler) processCustoms179Signature(ctx context.Context, req Customs179Request, requestID string, logger *slog.Logger) Customs179Response {
	guid := uuid.New().String()

	// Convert objects to JSON strings for the signature data format
	buf := &bytes.Buffer{}
	enc := json.NewEncoder(buf)
	enc.SetEscapeHTML(false) // disable escaping
	err := enc.Encode(req.PayExchangeInfoHead)
	if err != nil {
		logger.Error("Failed to marshal payExchangeInfoHead", "error", err)
		return Customs179Response{
			SessionID:   req.SessionID,
			ServiceTime: req.ServiceTime,
			GUID:        guid,
			Success:     false,
			Error:       "Failed to process payExchangeInfoHead: " + err.Error(),
		}
	}

	headJSON := strings.TrimSpace(buf.String())

	var listsJSON string
	if req.PayExchangeInfoLists != nil {
		buf.Reset()
		enc = json.NewEncoder(buf)
		enc.SetEscapeHTML(false) // disable escaping
		err := enc.Encode(req.PayExchangeInfoLists)
		if err != nil {
			logger.Error("Failed to marshal payExchangeInfoLists", "error", err)
			return Customs179Response{
				SessionID:   req.SessionID,
				ServiceTime: req.ServiceTime,
				GUID:        guid,
				Success:     false,
				Error:       "Failed to process payExchangeInfoLists: " + err.Error(),
			}
		}
		listsJSON = strings.TrimSpace(buf.String())
	} else {
		listsJSON = "[]"
	}

	// pass raw concatenated string as inData
	dataInfo179 := fmt.Sprintf(`"sessionID":"%s"||"payExchangeInfoHead":"%s"||"payExchangeInfoLists":"%s"||"serviceTime":"%d"`,
		req.SessionID, headJSON, listsJSON, req.ServiceTime)

	signResp, err := h.ukeyClient.CallWithRequestID(
		protocol.MethodSignDataAsPEM,
		map[string]interface{}{"inData": dataInfo179},
		requestID,
	)
	if err != nil {
		logger.Error("179 UKey communication error", "error", err)
		return Customs179Response{
			SessionID:   req.SessionID,
			ServiceTime: req.ServiceTime,
			GUID:        guid,
			Success:     false,
			Error:       "UKey communication error: " + err.Error(),
		}
	}
	if !signResp.Args.Result {
		msg := joinUKeyErrors(signResp.Args.Error)
		logger.Warn("179 UKey signing failed", "error", msg)
		if h.webhookService != nil {
			go h.webhookService.NotifyOperationFailure(ctx, "customs179", signResp.Args.Error)
		}
		return Customs179Response{
			SessionID:   req.SessionID,
			ServiceTime: req.ServiceTime,
			GUID:        guid,
			Success:     false,
			Error:       msg,
		}
	}

	signValue := extractStringAt(signResp.Args.Data, 0)
	certNo := extractStringAt(signResp.Args.Data, 1)

	resp := Customs179Response{
		SessionID:   req.SessionID,
		ServiceTime: req.ServiceTime,
		GUID:        guid,
		CertNo:      certNo,
		SignValue:   signValue,
		Success:     true,
	}

	if req.RealTimeDataUpload {
		payload, err := packPayExInfoStr(
			req.SessionID,
			headJSON,
			listsJSON,
			fmt.Sprintf("%d", req.ServiceTime),
			certNo,
			signValue,
		)
		if err != nil {
			logger.Error("Pack customs payload failed", "error", err)
			resp.Success = false
			resp.Error = "Failed to prepare customs submission: " + err.Error()
			return resp
		}
		customsResp, err := h.realTimeDataUpload(ctx, payload)
		if err != nil {
			logger.Error("Customs API submission failed", "error", err)
			if h.webhookService != nil {
				go h.webhookService.NotifyOperationFailure(ctx, "customs_api", []string{err.Error()})
			}
			resp.Success = false
			resp.Error = "Customs submission failed: " + err.Error()
			return resp
		}
		resp.Customs = customsResp
	}

	logger.Info("179 signature done", "sessionID", req.SessionID, "guid", guid)
	return resp
}

func packPayExInfoStr(sessionID, headJSON, listsJSON, serviceTime string, certNo, signValue string) (string, error) {
	var head json.RawMessage
	if err := json.Unmarshal([]byte(headJSON), &head); err != nil {
		return "", fmt.Errorf("failed to parse payExchangeInfoHead JSON: %w", err)
	}

	var lists json.RawMessage
	if strings.TrimSpace(listsJSON) != "" {
		if err := json.Unmarshal([]byte(listsJSON), &lists); err != nil {
			return "", fmt.Errorf("failed to parse payExchangeInfoLists JSON: %w", err)
		}
	} else {
		lists = json.RawMessage("[]")
	}

	payload := map[string]interface{}{
		"sessionID":            sessionID,
		"payExchangeInfoHead":  head,
		"payExchangeInfoLists": lists,
		"serviceTime":          serviceTime,
		"certNo":               certNo,
		"signValue":            signValue,
	}

	buf := &bytes.Buffer{}
	enc := json.NewEncoder(buf)
	enc.SetEscapeHTML(false) // disable escaping
	err := enc.Encode(payload)
	if err != nil {
		return "", fmt.Errorf("failed to marshal payload: %w", err)
	}

	return strings.TrimSpace(buf.String()), nil
}

func (h *ProxyHandler) realTimeDataUpload(ctx context.Context, data string) (map[string]interface{}, error) {
	const customsURL = "https://customs.chinaport.gov.cn/ceb2grab/grab/realTimeDataUpload"
	form := url.Values{}
	form.Set("payExInfoStr", data)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, customsURL, strings.NewReader(form.Encode()))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Cache-Control", "no-cache")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to execute request: %w", err)
	}
	defer res.Body.Close()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response body: %w", err)
	}
	if res.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("customs API returned status %d, response: %s", res.StatusCode, string(body))
	}

	var out map[string]interface{}
	if err := json.Unmarshal(body, &out); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}
	return out, nil
}

// HandleCustoms179 handles the /api/customs179 endpoint for signing and optional submission
func (h *ProxyHandler) HandleCustoms179(c *gin.Context) {
	requestID, logger, started := h.startRequest(c)

	var req Customs179Request
	if err := c.ShouldBindJSON(&req); err != nil {
		logger.Warn("Invalid 179 request", "error", err)
		resp := Customs179Response{Success: false, Error: "Invalid request format: " + err.Error()}
		h.sendAudit(c.Request.Context(), requestID, "customs179", nil, resp, false, resp.Error, started)
		c.JSON(http.StatusBadRequest, resp)
		return
	}

	logger.Info("179 request received", "sessionId", req.SessionID, "hasPayLists", req.PayExchangeInfoLists != nil, "upload", req.RealTimeDataUpload)

	resp := h.processCustoms179Signature(c.Request.Context(), req, requestID, logger)
	status := httpStatusFromError(resp.Error)

	h.sendAudit(c.Request.Context(), requestID, "customs179", req, resp, resp.Success, resp.Error, started)
	c.JSON(status, resp)
}
